{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Inheriting from type"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the last lectures, we saw how classes can be created by calling the `type` class.\n",
    "\n",
    "But what if we wanted to use something other than `type` to construct classes?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Since `type` is a class, maybe we could define a class that inherits from `type` (so we can leverage the actual type creation process), and override some things that would enable us to inject something in the class creation process."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here we want to intercept the creation of the `type` instance before it is created, so we would want to use the `__new__` method."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Remember that the `__new__` method basically needs to build and return the new instance. So we'll do the customizations we want, but ultimately we'll punt (delegate) to the `type` class to do the actual work, just adding the tweaks (before and/or after the class creation) we want."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Just a quick reminder of how the static `__new__` method works in general:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Test:\n",
    "    def __new__(cls, *args, **kwargs):\n",
    "        print(f'New instance of {cls} being created with these values:', args, kwargs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "New instance of <class '__main__.Test'> being created with these values: (10, 20) {'kw': 'a'}\n"
     ]
    }
   ],
   "source": [
    "t = Test(10, 20, kw='a')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And it's really the same as doing this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "New instance of <class '__main__.Test'> being created with these values: (10, 20) {'kw': 'a'}\n"
     ]
    }
   ],
   "source": [
    "Test.__new__(Test, 10, 20, kw='a')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Of course, it's now up to us to return an object from the `__new__` function."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So, instead of calling `type` to create the class (type), let's create a custom type generator by subclassing `type`.\n",
    "\n",
    "We'll inherit from `type`, and override the `__new__` function to create the instance of the class."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "import math\n",
    "\n",
    "class CustomType(type):\n",
    "    def __new__(cls, name, bases, class_dict):\n",
    "        # above is the signature that type.__new__ has - \n",
    "        # and args are collected and passed by Python when we call a class (to create an instance of that class)\n",
    "        # we'll see where those actually come from later\n",
    "        print('Customized type creation!')\n",
    "        cls_obj = super().__new__(cls, name, bases, class_dict)  # delegate to super (type in this case)\n",
    "        cls_obj.circ = lambda self: 2 * math.pi * self.r  # basically attaching a function to the class\n",
    "        return cls_obj"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's go through the same process to create our `Circle` class that we used in the last lecture, the manual way, but using `CustomType` instead of `type`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "class_body = \"\"\"\n",
    "def __init__(self, x, y, r):\n",
    "    self.x = x\n",
    "    self.y = y\n",
    "    self.r = r\n",
    "\n",
    "def area(self):\n",
    "    return math.pi * self.r ** 2\n",
    "\"\"\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And we create our class dictionary by executing the above code in the context of that dictionary:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "class_dict = {}\n",
    "exec(class_body, globals(), class_dict)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then we create the `Circle` class:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Customized type creation!\n"
     ]
    }
   ],
   "source": [
    "Circle = CustomType('Circle', (), class_dict)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We basically customized the class creation, and `Circle` is just a standard object, but, as you can see below, the type of our class is no longer `type`, but `CustomType`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "__main__.CustomType"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "type(Circle)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Of course, `Circle` is still an instance of `type` since `CustomType` is a subclass of `type`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(True, True)"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "isinstance(Circle, CustomType), issubclass(CustomType, type)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And just like before, `Circle` still has the `__init__` and `area` methods:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(True, True)"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "hasattr(Circle, '__init__'), hasattr(Circle, 'area')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So we can use `Circle` just as normal:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "c = Circle(0, 0, 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3.141592653589793"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "c.area()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Additionally, we injected a new function, `circ`, into the class while we were constructing it in the `__new__` method of `CustomType`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "hasattr(Circle, 'circ')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "6.283185307179586"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "c.circ()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So, this is another example of metaprogramming!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But yeah, creating classes (types) this way is a bit tedious!!!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is where the concept of a `metaclass` comes in, which we'll cover in the next set of lectures."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
